#pragma once

#if defined(__linux__) || defined(__APPLE__)
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#elif _WIN32
#include <WinSock2.h>
#include <sys/types.h>
#include <windows.h>
// #pragma comment(lib, "wsock32.lib")
#pragma comment(lib, "WS2_32")
#endif

#include <string>
#include <functional>
#include <cerrno>

#define FDR_UNUSED(expr){ (void)(expr); } 
#define FDR_ON_ERROR std::function<void(int, std::string)> onError = [](int errorCode, std::string errorMessage){FDR_UNUSED(errorCode); FDR_UNUSED(errorMessage)}
#define TMP_BUFFER_SIZE 0xFFFF

typedef unsigned int SocketLENT;
// #if defined(__linux__) || defined(__APPLE__)
// #elif _WIN32
// #endif


class cpSocket{
public:
#if defined(__linux__) || defined(__APPLE__)
    int sock;
    cpSocket(){
        sock=0;
    }
    int CreatSocket(int _domain, int _type, int _protocol){
        return sock = socket(_domain, _type, _protocol);
    }
    void setSocket(int socketId){
        sock=socketId;
    }
    int CloseSocket(){
        return close(this->sock);
    }
    int SetSocketOption(int __fd, int __level, int __optname, const void *__optval, SocketLENT __optlen){
        return setsockopt(__fd,__level,__optname,__optval,__optlen);
    }
#elif _WIN32

#endif
};
class BaseSocket
{
// Definitions
public:
    enum SocketType
    {
        TCP = SOCK_STREAM,
        UDP = SOCK_DGRAM
    };
    const uint16_t BUFFER_SIZE = 0xFFFF;
    sockaddr_in address;
    bool isClosed = false;

protected:
    int sock = 0;
    static std::string ipToString(sockaddr_in addr)
    {
        char ip[INET_ADDRSTRLEN];
        
        inet_ntop(AF_INET, &(addr.sin_addr), ip, INET_ADDRSTRLEN);

        return std::string(ip);
    }  

    BaseSocket(FDR_ON_ERROR, SocketType sockType = TCP, int socketId = -1)
    {
        if (socketId < 0)
        {
            if ((this->sock = socket(AF_INET, sockType, 0)) < 0)
            {
                onError(errno, "Socket creating error.");
            }
        }
        else
        {
            this->sock = socketId;
        }
    }

// Methods
public:
    virtual void Close() {
        if(isClosed) return;

        isClosed = true;
        close(this->sock);
    }

    std::string remoteAddress() {return ipToString(this->address);}
    int remotePort() {return ntohs(this->address.sin_port);}
    int fileDescriptor() const { return this->sock; }
};

/*
INET_ADDRSTRLEN
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46




inet_ntop
const char *inet_ntop(int __af, const void *__restrict__ __cp, char *__restrict__ __buf, socklen_t __len) throw()
Convert a Internet address in binary network format for interface
type AF in buffer starting at CP to presentation form and place
result in buffer of length LEN astarting at BUF.







close
*/